package com.penglecode.netty.client;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.util.concurrent.Executors;

import org.jboss.netty.bootstrap.ClientBootstrap;
import org.jboss.netty.channel.Channel;
import org.jboss.netty.channel.ChannelFactory;
import org.jboss.netty.channel.ChannelFuture;
import org.jboss.netty.channel.ChannelFutureListener;
import org.jboss.netty.channel.Channels;
import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.penglecode.netty.NettyException;

public class NettyClient implements NettyProxyCreator, AutoCloseable {

	private static final Logger logger = LoggerFactory.getLogger(NettyClient.class);
	
	private static final ChannelFactory channelFactory = new NioClientSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
	
	private String privateKey;
	
	private ClientBootstrap clientBootstrap;
	
	private SocketAddress remoteServerAddress;
	
	private Channel connectionChannel;
	
	public SocketAddress getRemoteServerAddress() {
		return remoteServerAddress;
	}

	public SocketAddress getLocalAddress() {
		return connectionChannel.getLocalAddress();
	}
	public String getPrivateKey() {
		return privateKey;
	}

	public void setPrivateKey(String privateKey) {
		this.privateKey = privateKey;
	}
	
	public NettyClient(SocketAddress InetSocketAddress, String privateKey) {
		this.privateKey = privateKey;
		this.remoteServerAddress = InetSocketAddress;
		this.clientBootstrap = new ClientBootstrap(channelFactory);
		this.clientBootstrap.setPipelineFactory(new NettyClientChannelPipelineFactory());
		this.init();
		ChannelFuture connectionChannelFuture = this.clientBootstrap.connect(this.remoteServerAddress);
		this.connectionChannel = connectionChannelFuture.awaitUninterruptibly().getChannel();
		if(connectionChannelFuture.isCancelled()){
			logger.warn("Connection attempt cancelled by user!");
		}else if(!connectionChannelFuture.isSuccess()){
			logger.warn("Connection established failure!", connectionChannelFuture.getCause());
		}
		new WaitReleaseResourceThread(this.connectionChannel).start();
	}
	
	public NettyClient(String serverHost, int port, String privateKey) {
		this(new InetSocketAddress(serverHost, port), privateKey);
	}
	
	protected void init(){
		this.clientBootstrap.setOption("connectTimeoutMillis", 3000);
		this.clientBootstrap.setOption("tcpNoDelay", true);
		this.clientBootstrap.setOption("keepAlive", true);
	}
	
	@SuppressWarnings("unchecked")
	public <T> T createProxy(Class<T> interfaceClass) {
		InvocationHandler handler = new NettyProxy(this, interfaceClass);
		return (T) Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{interfaceClass}, handler);
	}
	
	class WaitReleaseResourceThread extends Thread {
		
		private Channel connectionChannel;
		
		public WaitReleaseResourceThread(Channel connectionChannel) {
			super();
			this.connectionChannel = connectionChannel;
		}

		public void run() {
			connectionChannel.getCloseFuture().awaitUninterruptibly();
			clientBootstrap.shutdown();
		}
		
	}

	@SuppressWarnings("unchecked")
	protected <T> T execute(Object request) {
		ChannelFuture channelFuture = connectionChannel.write(request);
		if(channelFuture.isCancelled()){
			logger.error("Operation cancelled by user while attempt write message to the server!");
			throw new NettyException("Connection attempt cancelled by user!");
		}else if(!channelFuture.isSuccess()){
			logger.error("Attempt write message to the server has failed!");
			throw new NettyException("Attempt write message to the server has failed!", channelFuture.getCause());
		}
		return (T) channelFuture.getChannel().getAttachment();
	}

	public void close() throws Exception {
		if(this.connectionChannel != null){
			connectionChannel.close().awaitUninterruptibly();
			connectionChannel.getFactory().releaseExternalResources();
		}
	}
}
